#include <errno.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define RUN_COUNT			64

#define chk_err(func, retval, error) \
	do { if (retval != 0) { fprintf(stderr, "<%s>: %s\n", func, strerror(error)); exit(EXIT_FAILURE); } } \
	while (0)

#define chk_errno(func, retval)			chk_err(func, retval, errno)
#define chk_retval(func, retval)		chk_err(func, retval, retval)

typedef struct {
	struct timespec start;
	struct timespec end;
	double sum;
	size_t guardsize;
} runtime;

void segv_handler(int sig);
void *thread_func(void *arg);
void recursive_func(void);
void printruntime(const runtime *rt);

runtime runtimes[RUN_COUNT];
stack_t ss;
jmp_buf env;
int count = 0;

int main(void)
{
	int retval;
	int i;
	pthread_t tid;
	struct sigaction sigact;
	pthread_attr_t attr;
	long pagesize;
	char altstack[MINSIGSTKSZ];
	size_t guardsize;

#if defined(PAGESIZE) || defined(PAGE_SIZE)
#if defined(PAGESIZE)
	pagesize = PAGESIZE;
#else
	pagesize = PAGE_SIZE;
#endif
#elif defined(_SC_PAGESIZE) || defined(_SC_PAGE_SIZE)
	errno = 0;

#if defined(_SC_PAGESIZE)
	pagesize = sysconf(_SC_PAGESIZE);
#else
	pagesize = sysconf(_SC_PAGE_SIZE);
#endif

	chk_errno("sysconf", errno);
#else

#error Page size is not defined.
#endif

	ss.ss_sp = altstack;
	ss.ss_size = sizeof(altstack);
	ss.ss_flags = 0;

	sigact.sa_sigaction = NULL;
	sigact.sa_handler = &segv_handler;
	sigfillset(&sigact.sa_mask);
	sigact.sa_flags = SA_ONSTACK;

	retval = sigaction(SIGSEGV, &sigact, NULL);
	chk_errno("sigaction", retval);

	pthread_attr_init(&attr);

	for (count = 0; count < RUN_COUNT; ++count) {
		guardsize = (size_t)pagesize * count * 32;

		retval = pthread_attr_setguardsize(&attr, guardsize);
		chk_retval("pthread_attr_setguardsize", retval);

		retval = pthread_create(&tid, &attr, &thread_func, NULL);
		chk_retval("pthread_create", retval);

		retval = pthread_join(tid, NULL);
		chk_retval("pthread_join", retval);

		runtimes[count].sum = runtimes[count].end.tv_sec - runtimes[count].start.tv_sec +
							  (runtimes[count].end.tv_nsec / 1e+9 - runtimes[count].start.tv_nsec / 1e+9);
	}

	pthread_attr_destroy(&attr);

	for (i = 0; i < RUN_COUNT; ++i)
		printruntime(&runtimes[i]);
}

void *thread_func(void *arg)
{
	int retval;
	size_t guardsize;
	pthread_attr_t attr;

	retval = pthread_getattr_np(pthread_self(), &attr);
	chk_retval("pthread_getattr_np", retval);

	pthread_attr_getguardsize(&attr, &guardsize);
	runtimes[count].guardsize = guardsize;

	retval = sigaltstack(&ss, NULL);
	chk_errno("sigaltstack", retval);

	if (setjmp(env))
		pthread_exit(NULL);

	retval = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &runtimes[count].start);
	chk_errno("clock_gettime", retval);

	recursive_func();
}

void segv_handler(int sig)
{
	int retval;

	retval = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &runtimes[count].end);
	chk_errno("clock_gettime", retval);

	longjmp(env, 0);
}

void recursive_func(void)
{
	recursive_func();
}

void printruntime(const runtime *rt)
{
	printf("Guardsize:  %7lu | Overflow time:  %.6lf\n", rt->guardsize, rt->sum);
}
